package city.spring.configure.authorization;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.AccessTokenConverter;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;

import javax.annotation.PostConstruct;

/**
 * 自定义授权服务配置
 *
 * @author HouKunLin
 * @date 2019/12/1 0001 22:30
 */
@Configuration
@EnableAuthorizationServer
public class CustomAuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    private final static Logger logger = LoggerFactory.getLogger(CustomAuthorizationServerConfiguration.class);
    private final boolean isDebug = logger.isDebugEnabled();
    /**
     * 客户端信息Service，用来获取客户端Client信息
     */
    @Autowired
    private ClientDetailsService clientDetailsService;
    /**
     * 使用安全程序的认证管理器来为授权程序提供相关服务
     */
    @Autowired
    private AuthenticationManager authenticationManager;
    /**
     * 密码编码器
     */
    @Autowired
    private PasswordEncoder passwordEncoder;
    /**
     * 自定义异常捕获处理程序
     */
    @Autowired
    private WebResponseExceptionTranslator<OAuth2Exception> webResponseExceptionTranslator;
    /**
     * Token转换器
     */
    @Autowired
    private AccessTokenConverter accessTokenConverter;
    /**
     * Token存储
     */
    @Autowired
    private TokenStore tokenStore;
    /**
     * 拒绝访问处理程序
     */
    @Autowired
    private AccessDeniedHandler accessDeniedHandler;
    /**
     * 认证入口点
     */
    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;
    /**
     * 用户详细信息Service
     */
    @Autowired
    private UserDetailsService userDetailsService;
    /**
     * 授权码服务，用来存储授权码信息
     */
    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;
    /**
     * 默认TokenService
     */
    @Autowired
    private DefaultTokenServices defaultTokenServices;
    /**
     * 默认请求信息处理器工厂
     */
    @Autowired
    private OAuth2RequestFactory requestFactory;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 设置客户端信息Service，客户端信息直接从数据库中读取，不用硬编码在程序中
        clients.withClientDetails(clientDetailsService);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 开启密码授权类型，可以通过密码方式登录系统
        endpoints.authenticationManager(authenticationManager);
        // Token转换器
        endpoints.accessTokenConverter(accessTokenConverter);
        // Token存储器
        endpoints.tokenStore(tokenStore);
        // 异常转换器，已知：用户名错误、密码错误通过该处理器处理
        /// endpoints.exceptionTranslator(webResponseExceptionTranslator);
        endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
        // 要使用refresh_token的话，需要额外配置 userDetailsService
        // 必须设置自定义的用户信息服务，否则在刷新Token的时候提示 "UserDetailsService is required" 错误
        // 这是因为认证管理器 authenticationManager 与当前不一致的问题，或者没有提供 userDetailsService 导致的
        endpoints.userDetailsService(userDetailsService);
        // 设置客户端信息Service对象
        endpoints.setClientDetailsService(clientDetailsService);
        // 在数据库中存储 AuthorizationCode 信息
        endpoints.authorizationCodeServices(authorizationCodeServices);
        endpoints.requestFactory(requestFactory);
        // 使用自定义的TokenService对象
        endpoints.tokenServices(defaultTokenServices);
        // 使用 自定义的认证管理器提供的 PreAuthenticatedAuthenticationProvider 对象来处理获取授权码时的信息问题
        defaultTokenServices.setAuthenticationManager(authenticationManager);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                // 设置获取jwt Token key的权限，如果不需要认证就设置：permitAll()
                .tokenKeyAccess("permitAll()")
                // 设置检查Token是否有效的权限，permitAll()任何人都可以访问，isAuthenticated()需要使用Basic ClientID信息认证
                .checkTokenAccess("permitAll()")
                // 设置密码编码器
                .passwordEncoder(passwordEncoder)
                .accessDeniedHandler(accessDeniedHandler)
                .authenticationEntryPoint(authenticationEntryPoint)
                // 开启密码授权类型，系统会自动创建一个 ClientCredentialsTokenEndpointFilter 对象，并且该对象无法覆盖
                .allowFormAuthenticationForClients()
        ;
        //// 我试过了，试图自己创建 ClientCredentialsTokenEndpointFilter 对象时无法正常工作
        // 开启密码授权类型登录时需要提供 ClientCredentialsTokenEndpointFilter 对象，
        // 这里手动添加这个对象（不使用security.allowFormAuthenticationForClients()）是为了在客户端信息加载失败的时候可以抛出自定义的异常信息
        // ClientCredentialsTokenEndpointFilter clientCredentialsTokenEndpointFilter = new ClientCredentialsTokenEndpointFilter();
        // clientCredentialsTokenEndpointFilter.setAuthenticationManager(authenticationManager);
        // clientCredentialsTokenEndpointFilter.setAuthenticationEntryPoint(authenticationEntryPoint);
        // security.addTokenEndpointAuthenticationFilter(clientCredentialsTokenEndpointFilter);
    }

    @PostConstruct
    public void postConstruct() {
        logger.debug("自定义授权服务器配置: {}", this);
    }
}
